#ifndef NETWORK_H
#define NETWORK_H

#include <Raknet/PacketEnumerations.h>
#include <Raknet/RakNetworkFactory.h>
#include <Raknet/RakClientInterface.h>
#include <Raknet/RakServerInterface.h>
#include <Raknet/NetworkTypes.h>
#include <Raknet/BitStream.h>
#include <Raknet/NetworkIDGenerator.h>

#include <utils/String.h>
#include <utils/Singleton.h>

#include <iostream>
#include <vector>
#include <map>

class CNetworkObject;
class ClientEvent;
class ServerEvent;

class CNetIDGenerator : public /* (Raknet) */ NetworkIDGenerator
{
public:
    virtual bool IsNetworkIDAuthority (void) const;
    virtual bool RequiresSetParent (void) const         { return true; }
};

class CNetworked
{
public:
    CNetworked()
    {
        m_NetIDGenerator.SetParent((void*)this);
    }
    NetworkIDGenerator& getNetIDGenerator()      { return m_NetIDGenerator; }
protected:
    CNetIDGenerator m_NetIDGenerator;
};
/*
class CNetworkObject : public CNetworked, public CGameObject
{
public:
    CNetworkObject() : CNetworked(), CGameObject()
    {
        //NOTE: We needed to do this again because multiple inheritance does funny things with the addresses
        m_NetIDGenerator.SetParent((void*)this);
    }
    virtual void updateFromBitStream(RakNet::BitStream bsData);
    virtual void generateUpdateBitStream(RakNet::BitStream& bsObjectData);
private:
};
*/
//TODO: Work out why can't have common base class and then pick derived client or server during
//		runtime. Factors to consider include host\join functions, server acting as client, ...
//		Also, check CNetIDGenerator::IsNetworkIDAuthority() definition.
//		Could have client\server definition in Game object instead?
//			Client and Server objects in Game class?
//		Could also not have common base class if not necessary and simply have two different classes
//		Think about (and see atm) how components of the game will need to determine whether client or server (e.g., Input).

//TODO: Work out how to plug in level 2 code to handle custom packet types.

class Multiplayer : public Singleton<Multiplayer>
{
	//(Only) allow the singleton to access the constructor
    friend class Singleton<Multiplayer>;
    Multiplayer();
    
    public:
        
        void host(unsigned short iMaxPlayers, unsigned short iPort);
        void join(const char* cpHostIP, unsigned short iServerPort, unsigned short iClientPort);
        void checkForPackets();
        //void update();
        void disconnect();
        
        bool isServer()      { return m_bIsServer; }
        
        //CNetworkObject* addObject(CNetworkObject* pObject)      { m_pObjects.push_back(pObject); return m_pObjects.back(); }
        
        //TODO: Check if these are really needed
        RakServerInterface* getServer()         { return m_pServer; }
        RakClientInterface* getClient()         { return m_pClient; }
        
        //CNetworked* addNetworked(CNetworked* pObject);
        //TODO: Check where or if this is used
        //CNetworked* getNetworked(int index)         { return m_pObjects[index]; }
        
        //Registers an event and returns its unique packet identifier (-1 on
        // fail)
        template< class T >
        int registerClientEvent()
        {
			//TODO: Consider adding a 'name' concept to network events for ease
			//      of debugging.
			
			//Check that we don't go over the limit of packet IDs
			if ( 256 == ID_USER_PACKET_ENUM + m_pClientResponseEventArray.size() ) {
				std::cerr << "Error in Multiplayer::registerClientEvent: Max \
number of client events reached, no more can be added. You need to cull your \
network events."
				          << std::endl;
				return -1;
			}
			
			//Create a new event instance to use to respond to a received packet
			ClientEvent* p = new T();
			if ( !p ) {
				std::cerr << "Error in Multiplayer::registerClientEvent: \
Failed to create instance of response event." << std::endl;
				return -1;
			}
			
			//Add the reponse event instance to the array
			m_pClientResponseEventArray.push_back(p);
			std::cout << "Registered custom event #" << m_pClientResponseEventArray.size() << std::endl;
			//Return the index of the new event-type as its unique packet ID
			return m_pClientResponseEventArray.size() - 1;
		}
		
		//Registers an event and returns its unique packet identifier (-1 on
        // fail)
        template< class T >
        int registerServerEvent()
        {
			//TODO: Consider adding a 'name' concept to network events for ease
			//      of debugging.
			
			//Check that we don't go over the limit of packet IDs
			if ( 256 == ID_USER_PACKET_ENUM + m_pServerResponseEventArray.size() ) {
				std::cerr << "Error in Multiplayer::registerServerEvent: Max \
number of client events reached, no more can be added. You need to cull your \
network events."
				          << std::endl;
				return -1;
			}
			
			//Create a new event instance to use to respond to a received packet
			ServerEvent* p = new T();
			if ( !p ) {
				std::cerr << "Error in Multiplayer::registerServerEvent: \
Failed to create instance of response event." << std::endl;
				return -1;
			}
			
			//Add the reponse event instance to the array
			m_pServerResponseEventArray.push_back(p);
			std::cout << "Registered custom event #" << m_pServerResponseEventArray.size() << std::endl;
			//Return the index of the new event-type as its unique packet ID
			return m_pServerResponseEventArray.size() - 1;
		}
        
    private:
        
        std::vector< ClientEvent* > m_pClientResponseEventArray;
        std::vector< ServerEvent* > m_pServerResponseEventArray;
        
        void updateObjectFromBitStream(RakNet::BitStream& bsData);
        
        //Responses to core multiplayer messages
        void onClientConnect(PlayerID pidPlayer);
        //void onServerDisconnect(...);
        
        RakServerInterface* m_pServer;
        RakClientInterface* m_pClient;
        
        bool m_bIsServer;

        //Array of all network game objects
        //NOTE: These can be retrieved given a NetworkID via
		//		(CNetworkObject*) NetworkIDGenerator::GET_OBJECT_FROM_ID(nidObjectID);.
        //std::vector<CNetworkObject*> m_pObjects;
        
};

#endif //NETWORK_H
